﻿// <copyright file="WebtrendsAnalytics.cs" company="Microsoft Corporation">
// Copyright (c) 2009 Microsoft Corporation All Rights Reserved
// </copyright>
// <author>Michael S. Scherotter</author>
// <email>mischero@microsoft.com</email>
// <date>2009-11-19</date>
// <summary>WebtrendsAnalytics class definition</summary>

namespace Webtrends.WebAnalytics
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel.Composition;
    using System.Globalization;
    using System.IO;
    using System.IO.IsolatedStorage;
    using System.Linq;
    using System.Net;
#if SILVERLIGHT
    using System.Net.Browser;
#endif
    using System.Security;
    using System.Text;
    using System.Windows;
#if SILVERLIGHT && !WINDOWS_PHONE
    using System.Windows.Browser;
#endif
    using System.Windows.Interactivity;
    using Microsoft.WebAnalytics;
    using Microsoft.WebAnalytics.Data;
    using Webtrends.WebAnalytics.Data;

    /// <summary>
    /// Webtrends Data Collection
    /// </summary>
    /// <remarks>http://product.webtrends.com/dcapi/index.html</remarks>
    public class WebtrendsAnalytics : Behavior<FrameworkElement>
    {
        #region Fields
        /// <summary>
        /// The Isolated storage key
        /// </summary>
        private const string VisitorIdKey = "Webtrends.WebAnalytics.VisitorId";
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the WebtrendsAnalytics class.
        /// </summary>
        public WebtrendsAnalytics()
        {
            this.ApiVersion = Properties.Resources.ApiVersion;

            this.CustomParameters = new Collection<PropertyValue>();

#if DEBUG
            this.Verbose = true;
#else
            this.Verbose = false;
#endif
        }
        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the Web Analytics events
        /// </summary>
        [Import]
        public WebAnalyticsEvents Events { get; set; }

        /// <summary>
        /// Gets or sets the API Version
        /// </summary>
        public string ApiVersion { get; set; }

        /// <summary>
        /// Gets or sets the data source Id
        /// </summary>
        ////[RegularExpression("dcs[*]_[4]")]
        public string DataSourceId { get; set; }

        /// <summary>
        /// Gets or sets the content group
        /// </summary>
        public string ContentGroup { get; set; }

        /// <summary>
        /// Gets or sets application-specific data
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This needs to be public so it can be set in XAML.")]
        public Collection<PropertyValue> CustomParameters { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether to use verbose error messages
        /// </summary>
        /// <remarks>in DEBUG builds, this is true by default, in RELEASE builds, it is false by default</remarks>
        public bool Verbose { get; set; }

#if SILVERLIGHT
        /// <summary>
        /// Gets or sets the visitor Id
        /// </summary>
        private static string VisitorId
        {
            get
            {
                try
                {
                    if (IsolatedStorageSettings.ApplicationSettings.Contains(VisitorIdKey))
                    {
                        return IsolatedStorageSettings.ApplicationSettings[VisitorIdKey] as string;
                    }
                }
                catch (IsolatedStorageException ise)
                {
                    System.Diagnostics.Debug.WriteLine("Error getting visitor Id from isolated storage: {0}", ise.Message);
                }

                return null;
            }

            set
            {
                try
                {
                    IsolatedStorageSettings.ApplicationSettings[VisitorIdKey] = value;
                }
                catch (IsolatedStorageException ise)
                {
                    System.Diagnostics.Debug.WriteLine("Error seting visitor Id to isolated storage: {0}", ise.Message);
                }
            }
        }
#else
        /// <summary>
        /// Gets or sets the visitor Id
        /// </summary>
        private static string VisitorId
        {
            get
            {
                return Properties.Settings.Default.VisitorIdKey;
            }

            set
            {
                Properties.Settings.Default.VisitorIdKey = value;

                Properties.Settings.Default.Save();
            }
        }
#endif
        #endregion

        #region Methods
        /// <summary>
        /// Attach the TrackEvent handler and get the visitor Id
        /// </summary>
        protected override void OnAttached()
        {
            base.OnAttached();

#if SILVERLIGHT
            CompositionInitializerEx.SatisfyImports(this);

            WebRequest.RegisterPrefix("http://dc.webtrends.com", WebRequestCreator.ClientHttp);

            WebRequest.RegisterPrefix("https://dc.webtrends.com", WebRequestCreator.ClientHttp);
#else
            PartInitializer.SatisfyImports(this);
#endif
            if (!string.IsNullOrEmpty(this.DataSourceId))
            {
                this.Events.TrackEvent += new EventHandler<AnalyticsEventArgs>(this.EventDispatcher_TrackEvent);

                this.GetVisitorId();
            }

            this.AssociatedObject.Unloaded += new RoutedEventHandler(delegate(object sender, RoutedEventArgs args)
            {
                System.Windows.Interactivity.Interaction.GetBehaviors(this.AssociatedObject).Remove(this);
            });
        }

        /// <summary>
        /// Detach the TrackEvent handler
        /// </summary>
        protected override void OnDetaching()
        {
            this.Events.TrackEvent -= new EventHandler<AnalyticsEventArgs>(this.EventDispatcher_TrackEvent);
        }
        #endregion

        #region Implementation

        /// <summary>
        /// Append the items to the string as query parameters
        /// </summary>
        /// <param name="builder">the string builder</param>
        /// <param name="items">a property value</param>
        private static void Append(StringBuilder builder, IEnumerable<PropertyValue> items)
        {
            if (items == null)
            {
                return;
            }

            foreach (var item in items)
            {
                builder.AppendFormat(
                    CultureInfo.InvariantCulture,
                    "{0}={1}&",
                    Uri.EscapeUriString(item.PropertyName),
                    Uri.EscapeUriString(item.Value));
            }
        }

        /// <summary>
        /// Trace a web exception to the debugger
        /// </summary>
        /// <param name="we">the web execption</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.IO.StreamReader", Justification = "For debugging")]
        private static void TraceException(WebException we)
        {
            var response = we.Response as HttpWebResponse;

            if (response == null)
            {
                System.Diagnostics.Debug.WriteLine("Web Exception: {0}", we.Message);
            }
            else
            {
                System.Diagnostics.Debug.WriteLine(response.StatusDescription);

                using (var stream = response.GetResponseStream())
                {
                    var reader = new StreamReader(stream);

                    System.Diagnostics.Debug.WriteLine(reader.ReadToEnd());
                }
            }
        }

        /// <summary>
        /// Gets the Visitor Id
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Irrelevant for Silverlight.")]
        private void GetVisitorId()
        {
            if (string.IsNullOrEmpty(this.DataSourceId))
            {
                return;
            }

            if (!string.IsNullOrEmpty(VisitorId))
            {
                return;
            }

            var requestUriString = string.Format(
                CultureInfo.InvariantCulture,
                "https://dc.webtrends.com/v{0}/{1}/ids.svc?dcsformat=plain&dcsverbose={2}",
                this.ApiVersion,
                this.DataSourceId,
                this.Verbose.ToString().ToLower(CultureInfo.InvariantCulture));

            System.Diagnostics.Debug.WriteLine(requestUriString);

            var webClient = new WebClient();

            webClient.UploadStringCompleted += new UploadStringCompletedEventHandler(this.OnPostedVisitorId);

            webClient.UploadStringAsync(new Uri(requestUriString, UriKind.Absolute), string.Empty);

            ////var webRequest = HttpWebRequest.Create(requestUriString);

            ////webRequest.Method = "POST";

            ////webRequest.BeginGetResponse(new AsyncCallback(this.OnReturnedVisitorIdentifier), webRequest);
        }

        /// <summary>
        /// POST to request the Visitor Id has returned
        /// </summary>
        /// <param name="sender">the WebClient</param>
        /// <param name="e">the upload string completed event arguments</param>
        private void OnPostedVisitorId(object sender, UploadStringCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                WebException we = e.Error as WebException;

                if (we == null)
                {
                    System.Diagnostics.Debug.WriteLine(e.Error.Message);
                }
                else
                {
                    TraceException(we);
                }

                return;
            }

            VisitorId = e.Result;
        }

        /// <summary>
        /// Track an event using Webtrends data collection API
        /// </summary>
        /// <remarks>http://developer.webtrends.com//docs/DOC-1151#cf</remarks>
        /// <param name="sender">the event dispatcher</param>
        /// <param name="e">an analytics event argument</param>
        private void EventDispatcher_TrackEvent(object sender, AnalyticsEventArgs e)
        {
            this.PostEventData(e.AnalyticsEvent);
        }

        /// <summary>
        /// Post the event data to Webtrends
        /// </summary>
        /// <param name="analyticsEvent">an analytics event</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Irrelevant for Silverlight.")]
        private void PostEventData(AnalyticsEvent analyticsEvent)
        {
            if (string.IsNullOrEmpty(this.DataSourceId))
            {
                return;
            }

            var requestUriString = string.Format(
                CultureInfo.InvariantCulture,
                "https://dc.webtrends.com/v{0}/{1}/events.svc?dcsverbose={2}",
                this.ApiVersion,
                this.DataSourceId,
                this.Verbose.ToString(CultureInfo.InvariantCulture).ToLower(CultureInfo.InvariantCulture));

            System.Diagnostics.Debug.WriteLine(requestUriString);

            ////var client = new WebClient();

            ////client.UploadStringCompleted += new UploadStringCompletedEventHandler(this.OnEventPosted);

            ////var content = this.GenerateParameters(analyticsEvent);

            ////client.UploadStringAsync(new Uri(requestUriString, UriKind.Absolute), content);

            ////return;

            var request = HttpWebRequest.Create(requestUriString) as HttpWebRequest;

            request.Method = "POST";

            request.ContentType = "application/x-www-form-urlencoded";

            var postData = new PostData()
            {
                Event = analyticsEvent,
                Request = request
            };

            request.BeginGetRequestStream(new AsyncCallback(this.OnEventRequestStream), postData);
        }

        /// <summary>
        /// fill the HTTP POST content
        /// </summary>
        /// <param name="result">the async result</param>
        private void OnEventRequestStream(IAsyncResult result)
        {
            var postData = result.AsyncState as PostData;

            using (var stream = postData.Request.EndGetRequestStream(result))
            {
                var writer = new StreamWriter(stream);
                var logEvent = postData.Event;

                var content = this.GenerateParameters(logEvent);

                System.Diagnostics.Debug.WriteLine(content);

                writer.Write(content);

                writer.Flush();
            }

            postData.Request.BeginGetResponse(new AsyncCallback(this.OnEventPosted), postData.Request);
        }

        /// <summary>
        /// Generate the parameters for the Log Event
        /// </summary>
        /// <param name="logEvent">the event to log</param>
        /// <returns>the paramter string</returns>
        private string GenerateParameters(AnalyticsEvent logEvent)
        {
            var builder = new StringBuilder();

            Append(builder, this.CustomParameters);

            Append(builder, logEvent.EventArgs);

#if WINDOWS_PHONE
            builder.AppendFormat(
                CultureInfo.InvariantCulture,
                "dcsuri={0}&",
                logEvent.DocumentUri);
#else
            builder.AppendFormat(
                CultureInfo.InvariantCulture,
                "dcsuri={0}&",
                logEvent.DocumentUri.LocalPath);
#endif

            builder.AppendFormat(
                CultureInfo.InvariantCulture,
                "dcsua={0}&",
                "Silverlight");

            builder.AppendFormat(
                CultureInfo.InvariantCulture,
                "WT.ti={0}&",
                Uri.EscapeUriString(logEvent.AppName));

            builder.AppendFormat(
                CultureInfo.InvariantCulture,
                "WT.tz={0}&",
                logEvent.Timestamp.Offset.TotalHours);

            if (!string.IsNullOrEmpty(this.ContentGroup))
            {
                builder.AppendFormat(
                    CultureInfo.InvariantCulture,
                    "WT.cg={0}&",
                    this);
            }

            if (!string.IsNullOrEmpty(VisitorId))
            {
                builder.AppendFormat(
                    CultureInfo.InvariantCulture,
                    "WT.co_f={0}&",
                    VisitorId);
            }

            if (!string.IsNullOrEmpty(logEvent.Language))
            {
                builder.AppendFormat(
                    CultureInfo.InvariantCulture,
                    "WT.ul={0}&",
                    logEvent.Language);
            }

            builder.AppendFormat(
                CultureInfo.InvariantCulture,
                "WT.sr={0}x{1}&",
                logEvent.Resolution.Width,
                logEvent.Resolution.Height);

            // Silverlight Version
            builder.AppendFormat(
                CultureInfo.InvariantCulture,
                "WT.slv={0}&",
                logEvent.SilverlightVersion);

            builder.AppendFormat(
                CultureInfo.InvariantCulture,
                "WT.cg_n={0}&Wt.co=yes",
                logEvent.ObjectName);

            builder.AppendFormat(
                CultureInfo.InvariantCulture,
                "&WT.ev={0}",
                logEvent.Name);

            builder.AppendFormat(
                CultureInfo.InvariantCulture,
                "&WT.av={0}",
                logEvent.Version);

            builder.AppendFormat(
                CultureInfo.InvariantCulture,
                "&WT.ets={0}",
                logEvent.Timestamp.ToUnixTimestamp());

#if WINDOWS_PHONE
            builder.Append("&WT.dm=Windows Phone&WT.os=7");
#endif

            var location = (from propertyValue in logEvent.EventArgs
                            where propertyValue.PropertyName == "Location"
                            select propertyValue.Value).FirstOrDefault();

            if (!string.IsNullOrEmpty(location))
            {
                builder.AppendFormat(
                    CultureInfo.InvariantCulture,
                    "&WT.gc={0}",
                    location);
            }

            var content = builder.ToString();

            return content;
        }

        /// <summary>
        /// An event has been posted
        /// </summary>
        /// <param name="result">the async result</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.IO.StreamReader", Justification = "For debugging")]
        private void OnEventPosted(IAsyncResult result)
        {
            try
            {
                var request = result.AsyncState as HttpWebRequest;

                var response = request.EndGetResponse(result);

                using (var stream = response.GetResponseStream())
                {
                    var reader = new StreamReader(stream);

                    System.Diagnostics.Debug.WriteLine("Posted event to WebTrends: {0}", reader.ReadToEnd());
                }
            }
            catch (WebException we)
            {
                TraceException(we);
            }
            catch (SecurityException se)
            {
                System.Diagnostics.Debug.WriteLine("Security Exception: {0}", se.Message);
            }
        }

        #endregion
    }
}
